package net.minecraft.client.data;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.render.model.json.BlockModelDefinition;
import net.minecraft.client.render.model.json.ModelVariantOperator;
import net.minecraft.client.render.model.json.WeightedVariant;
import net.minecraft.state.property.Property;

@Environment(EnvType.CLIENT)
public class VariantsBlockModelDefinitionCreator implements BlockModelDefinitionCreator {
	private final Block block;
	private final List<VariantsBlockModelDefinitionCreator.Entry> variants;
	private final Set<Property<?>> definedProperties;

	VariantsBlockModelDefinitionCreator(Block block, List<VariantsBlockModelDefinitionCreator.Entry> variants, Set<Property<?>> definedProperties) {
		this.block = block;
		this.variants = variants;
		this.definedProperties = definedProperties;
	}

	static Set<Property<?>> validateAndAddProperties(Set<Property<?>> definedProperties, Block block, BlockStateVariantMap<?> variantMap) {
		List<Property<?>> list = variantMap.getProperties();
		list.forEach(property -> {
			if (block.getStateManager().getProperty(property.getName()) != property) {
				throw new IllegalStateException("Property " + property + " is not defined for block " + block);
			} else if (definedProperties.contains(property)) {
				throw new IllegalStateException("Values of property " + property + " already defined for block " + block);
			}
		});
		Set<Property<?>> set = new HashSet(definedProperties);
		set.addAll(list);
		return set;
	}

	/**
	 * Appends a block state variant map to this block state information.
	 * 
	 * <p>A block state variant map defines some of the variant settings based
	 * on a defined set of properties in the block state, such as the model
	 * of the block state is determined by a coordinated map of power and
	 * machine type property, and the y rotation determined by a facing
	 * property, etc.
	 * 
	 * @return this block state file
	 */
	public VariantsBlockModelDefinitionCreator coordinate(BlockStateVariantMap<ModelVariantOperator> variantMap) {
		Set<Property<?>> set = validateAndAddProperties(this.definedProperties, this.block, variantMap);
		List<VariantsBlockModelDefinitionCreator.Entry> list = this.variants.stream().flatMap(variant -> variant.apply(variantMap)).toList();
		return new VariantsBlockModelDefinitionCreator(this.block, list, set);
	}

	public VariantsBlockModelDefinitionCreator apply(ModelVariantOperator operator) {
		List<VariantsBlockModelDefinitionCreator.Entry> list = this.variants.stream().flatMap(variant -> variant.apply(operator)).toList();
		return new VariantsBlockModelDefinitionCreator(this.block, list, this.definedProperties);
	}

	@Override
	public BlockModelDefinition createBlockModelDefinition() {
		Map<String, BlockStateModel.Unbaked> map = new HashMap();

		for (VariantsBlockModelDefinitionCreator.Entry entry : this.variants) {
			map.put(entry.properties.asString(), entry.variant.toModel());
		}

		return new BlockModelDefinition(Optional.of(new BlockModelDefinition.Variants(map)), Optional.empty());
	}

	@Override
	public Block getBlock() {
		return this.block;
	}

	public static VariantsBlockModelDefinitionCreator.Empty of(Block block) {
		return new VariantsBlockModelDefinitionCreator.Empty(block);
	}

	public static VariantsBlockModelDefinitionCreator of(Block block, WeightedVariant model) {
		return new VariantsBlockModelDefinitionCreator(block, List.of(new VariantsBlockModelDefinitionCreator.Entry(PropertiesMap.EMPTY, model)), Set.of());
	}

	@Environment(EnvType.CLIENT)
	public static class Empty {
		private final Block block;

		public Empty(Block block) {
			this.block = block;
		}

		public VariantsBlockModelDefinitionCreator with(BlockStateVariantMap<WeightedVariant> variantMap) {
			Set<Property<?>> set = VariantsBlockModelDefinitionCreator.validateAndAddProperties(Set.of(), this.block, variantMap);
			List<VariantsBlockModelDefinitionCreator.Entry> list = variantMap.getVariants()
				.entrySet()
				.stream()
				.map(entry -> new VariantsBlockModelDefinitionCreator.Entry((PropertiesMap)entry.getKey(), (WeightedVariant)entry.getValue()))
				.toList();
			return new VariantsBlockModelDefinitionCreator(this.block, list, set);
		}
	}

	@Environment(EnvType.CLIENT)
	record Entry(PropertiesMap properties, WeightedVariant variant) {

		public Stream<VariantsBlockModelDefinitionCreator.Entry> apply(BlockStateVariantMap<ModelVariantOperator> operatorMap) {
			return operatorMap.getVariants().entrySet().stream().map(variant -> {
				PropertiesMap propertiesMap = this.properties.copyOf((PropertiesMap)variant.getKey());
				WeightedVariant weightedVariant = this.variant.apply((ModelVariantOperator)variant.getValue());
				return new VariantsBlockModelDefinitionCreator.Entry(propertiesMap, weightedVariant);
			});
		}

		public Stream<VariantsBlockModelDefinitionCreator.Entry> apply(ModelVariantOperator operator) {
			return Stream.of(new VariantsBlockModelDefinitionCreator.Entry(this.properties, this.variant.apply(operator)));
		}
	}
}
